#include "imagedir_reader.h"
#include "atlas_utils.h"

using namespace std;

namespace {
    const int64_t kUsec = 1000000;
}


namespace {
    const uint32_t kDecodeFrameQueueSize = 256;
    const int kReadQueueOpWait = 10000; //每次等待10毫秒
    const int kQueueOpRetryTimes = 1000;
    const int kFrameEnQueueRetryTimes = 1000;//为了防止丢帧,入队最多等待 1ms
    const int kWaitReaderFinishInterval = 1000;

    const int kDefaultFps = 30;
}

ImageDirReader::ImageDirReader(const std::string& imageDirName, aclrtContext context)
:context_(context)
,frameId_(0)
,finFrameCnt_(0)
,status_(READER_UNINIT)
,streamName_(imageDirName)
,lastReadTime_(0)
,fps_(kDefaultFps)          // 默认100ms
,frameImageQueue_(kDecodeFrameQueueSize)
,isStop_(false)
,isReleased_(false){}

ImageDirReader::~ImageDirReader() {
    DestroyResource();
}

void ImageDirReader::DestroyResource() {
    if (isReleased_) return;    
   
    isStop_ = true;

    dvpp_.DestroyResource();

    AtlasError ret;
    if (stream_ != nullptr) {
        ret = aclrtDestroyStream(stream_);
        if (ret != ACL_ERROR_NONE) {
            ERROR_LOG("destroy stream failed");
        }
        stream_ = nullptr;
    }
    INFO_LOG("end to destroy stream");

    while ((status_ >= READER_START) && (status_ < READER_DIR_FINISHIED))
        usleep(kWaitReaderFinishInterval);

    
    do {
        shared_ptr<ImageData> frame = FrameImageOutQueue(true);
        if (frame == nullptr)  {
            break;
        }

        if (frame->data != nullptr) {
            acldvppFree(frame->data.get());
            frame->data = nullptr;
        }       
    }while(1);
    isReleased_ = true;
}

AtlasError ImageDirReader::InitResource() {
    aclError aclRet;

    if (context_ == nullptr) {
        aclRet = aclrtGetCurrentContext(&context_);
        if ((aclRet != ACL_ERROR_NONE) || (context_ == nullptr)) {
            ATLAS_LOG_ERROR("Get current acl context error:%d", aclRet);
            return ATLAS_ERROR_SET_ACL_CONTEXT;
        }       
    }
    
    //如果有传入acl context参数,设置解码器主线程context
    AtlasError ret = SetAclContext();
    if (ret != ATLAS_OK) {
        ATLAS_LOG_ERROR("Set video decoder acl context error:%d", aclRet);
        return ret;
    }

    aclRet = aclrtGetRunMode(&runMode_);
    if (aclRet != ACL_ERROR_NONE) {
        ATLAS_LOG_ERROR("acl get run mode failed");
        return ATLAS_ERROR_GET_RUM_MODE;
    } 


    return ATLAS_OK;  
}


AtlasError ImageDirReader::Open() {
    //防止多次初始化
    if (status_ == READER_ERROR) 
        return ATLAS_ERROR_OPEN_IMAGE_READER_UNREADY;

    if (status_ != READER_UNINIT)
        return ATLAS_OK;

    AtlasError ret = InitResource();
    if (ret != ATLAS_OK) {
        this->SetStatus(READER_ERROR);
        ATLAS_LOG_ERROR("Open %s failed for init resource error: %d", 
                        streamName_.c_str(), ret);
        return ret;
    }
    
    // create stream
    ret = aclrtCreateStream(&stream_);
    if (ret != ACL_ERROR_NONE) {
        ERROR_LOG("acl create stream failed");
        return FAILED;
    }
    INFO_LOG("create stream success");
    ret = dvpp_.InitResource(stream_);
    if (ret != ATLAS_OK) {
        this->SetStatus(READER_ERROR);
        ATLAS_LOG_ERROR("Open %s failed for init dvpp error: %d", 
                        streamName_.c_str(), ret);
        return ret;
    }

    //读一张图片，获取图像信息
    vector<string> fileVec;
    GetAllFiles(streamName_, fileVec);
    if (fileVec.empty() || !IsImageFile(fileVec[0])) {
        this->SetStatus(READER_ERROR);
        ATLAS_LOG_ERROR("Open %s failed for there is no image error: %d", 
                        streamName_.c_str(), ret);
        return ret;
    }
    ImageData image;
    ret = ReadJpeg(image, fileVec[0]);
    if (image.data == nullptr) {
        ERROR_LOG("Read image %s failed", fileVec[0].c_str());
        return ret;
    }
    imageWidth_ = image.width;
    imageHeight_ = image.height;
    fpsInterval_ = kUsec / fps_;
 
    this->SetStatus(READER_READY);
    ATLAS_LOG_INFO("Dir %s reader init ok", streamName_.c_str());

    return ATLAS_OK;
}

void ImageDirReader::ProcessDecodedImage(shared_ptr<ImageData> frameData) {
    FrameImageEnQueue(frameData);
}

AtlasError ImageDirReader::FrameImageEnQueue(shared_ptr<ImageData> frameData) {
    for (int count = 0; count < kFrameEnQueueRetryTimes; count++) {
        if (frameImageQueue_.Push(frameData)) 
            return ATLAS_OK;
        usleep(kReadQueueOpWait); 
    }
    ATLAS_LOG_ERROR("reader %s lost read image for queue full", 
	                streamName_.c_str());

    return ATLAS_ERROR_IMAGE_READER_QUEUE_FULL;
}


//启动ffmpeg解码器,将视频文件解码为h26x帧
void ImageDirReader::StartFrameReader() {
    if (status_ == READER_READY) {

        readerThread_ = thread(FrameReadThreadFunction, (void*)this);
        readerThread_.detach();

        status_ = READER_START;
    }
}

//图片文件读取线程入口
void ImageDirReader::FrameReadThreadFunction(void* readerSelf) {
    ImageDirReader* thisPtr =  (ImageDirReader*)readerSelf;

    // 将文件读取线程的context设置为何主线程一致
    aclError aclRet = thisPtr->SetAclContext();
    if (aclRet != ACL_ERROR_NONE) {
        ATLAS_LOG_ERROR("Set frame decoder context failed, errorno:%d",
                        aclRet);
        return;
    }
    //读文件,这是一个阻塞接口,一直到文件夹下的图片帧读完
    thisPtr->ImageRead(thisPtr);
    thisPtr->SetStatus(READER_DIR_FINISHIED);  
    // cout << "READER_DIR_FINISHIED.................." << endl;
}

void ImageDirReader::ImageRead(ImageDirReader* thisPtr) {
    vector<string> fileVec;
    GetAllFiles(thisPtr->streamName_, fileVec);

    sort(fileVec.begin(), fileVec.end());

    ImageData image;
    for (size_t i = 0; i<fileVec.size(); i++) {
        string imageFile = fileVec[i];

        if (thisPtr->IsStop()) {
            break;
        }
        if (!IsImageFile(imageFile)) {
            continue;
        }

        AtlasError ret = ReadJpeg(image, imageFile);
        if (image.data == nullptr) {
            ERROR_LOG("Read image %s failed", imageFile.c_str());
            continue;
        }
        // // 不能在此解码，原因未知
        ImageData yuvImage, cropImage;
        ImageData dvppImage;
        Utils::CopyImageDataToDvpp(dvppImage, image);
        ret = dvpp_.JpegD(yuvImage, dvppImage);
        if (ret != SUCCESS) {
            ERROR_LOG("%s Convert jpeg to yuv failed", imageFile.c_str());
            continue;
        }

        // 此处需要根据输入类型来采取不同对齐的裁剪方式
        ret = (Result)dvpp_.Crop(cropImage, yuvImage, 0, 0, yuvImage.width-1, yuvImage.height-1);
        if (ret == FAILED) {
            ERROR_LOG("Crop failed");
            continue;
        }

        shared_ptr<ImageData> pImage = make_shared<ImageData>();
        pImage->format =cropImage.format;
        pImage->width = cropImage.width;
        pImage->height = cropImage.height;
        pImage->alignWidth = cropImage.alignWidth;
        pImage->alignHeight = cropImage.alignHeight;
        pImage->size = cropImage.size;
        pImage->data = cropImage.data;
        pImage->id = i;
        pImage->name = imageFile;

        thisPtr->imageWidth_ = image.width;
        thisPtr->imageHeight_ = image.height;

        thisPtr->ProcessDecodedImage(pImage);

        // printf("dvpp jpgd format %d width %d, height %d, alignwidth %d, alignheight %d size %d\n", pImage->format, pImage->width, pImage->height, pImage->alignWidth, pImage->alignHeight, pImage->size);

        SleeptoNextFrameTime();
    }
}

void ImageDirReader::SleeptoNextFrameTime() {

    //获取当前时间
    timeval tv;
    gettimeofday(&tv, 0);
    int64_t now = (int64_t)tv.tv_sec * 1000000 + (int64_t)tv.tv_usec;

    if (lastReadTime_ == 0) {
        lastReadTime_ = now;
        return;
    }
    //计算到解码一帧后还剩余的时间
    int64_t lastInterval = (now - lastReadTime_);
    int64_t sleepTime = (lastInterval < fpsInterval_)?(fpsInterval_-lastInterval):0;
    //耗完一帧解码时间
    usleep(sleepTime);
    //记录下一帧解码的开始时间
    gettimeofday(&tv, 0);
    lastReadTime_ = (int64_t)tv.tv_sec * 1000000 + (int64_t)tv.tv_usec;

    return;
}

//当前解码器是否准备好
bool ImageDirReader::IsOpened() { 
    ATLAS_LOG_INFO("Dir %s read status %d", streamName_.c_str(), status_);
    return (status_ == READER_READY) || (status_ == READER_START);
}

/*读取一帧解码后的yuv图像*/
AtlasError ImageDirReader::Read(ImageData& image) {
    //如果当前解码器异常或者解码结束,则直接返回nullptr
    if (status_ == READER_ERROR) {
        ATLAS_LOG_ERROR("Read failed for dir %s failed", 
                        streamName_.c_str()); 
        return ATLAS_ERROR_IMAGE_READER_STATUS;
    }
    if (status_ == READER_FINISHED) {
        ATLAS_LOG_INFO("No frame to read for reader %s finished", 
                        streamName_.c_str()); 
        return ATLAS_ERROR_READER_FINISH;
    }
    //如果当前只是准备好,但是还未开始解码.Read的调用触发解码开始
    if (status_ == READER_READY) {
        StartFrameReader();
        usleep(kReadQueueOpWait);
    }

    //从解码后图片存放队列读取一帧图片
    bool noWait = (status_ == READER_DIR_FINISHIED);
    shared_ptr<ImageData> frame = FrameImageOutQueue(noWait);
    if ((status_ == READER_DIR_FINISHIED) && (frame == nullptr)) {
        SetStatus(READER_FINISHED);
        ATLAS_LOG_INFO("No frame to read anymore");
        return ATLAS_ERROR_READER_FINISH;
    }

    if (frame == nullptr) {
        ATLAS_LOG_ERROR("No frame image to read abnormally");
        return ATLAS_ERROR_READ_EMPTY;
    }
    image = *frame;
    ATLAS_LOG_INFO("image reader queue size: %d", frameImageQueue_.size());
    return ATLAS_OK;
}

shared_ptr<ImageData> ImageDirReader::FrameImageOutQueue(bool noWait) {

    shared_ptr<ImageData> image = frameImageQueue_.Pop();
    if (noWait || (image != nullptr)) return image;

    for (int count = 0; count < kQueueOpRetryTimes - 1 && GetStatus() != READER_DIR_FINISHIED; count++) {
        usleep(kReadQueueOpWait);

        image = frameImageQueue_.Pop();
        if (image != nullptr)
            return image;
    }

    return nullptr;
}

uint32_t ImageDirReader::Get(StreamProperty key) {
    uint32_t value = 0;

    switch(key){
        case FRAME_WIDTH:
            value = imageWidth_;
            break;
        case FRAME_HEIGHT:
            value = imageHeight_;
            break;
        case VIDEO_FPS:
            value = fps_;
            break;
        default:
            ATLAS_LOG_ERROR("Unsurpport property %d to get for video", key);
            break;
    }

    return value;
}


AtlasError ImageDirReader::SetAclContext() {
    if (context_ == nullptr) {
        ATLAS_LOG_ERROR("Video decoder context is null");
        return ATLAS_ERROR_SET_ACL_CONTEXT;
    }
    
    aclError ret = aclrtSetCurrentContext(context_);
    if (ret != ACL_ERROR_NONE) {
        ATLAS_LOG_ERROR("Video decoder set context failed, error: %d", ret);
        return ATLAS_ERROR_SET_ACL_CONTEXT;
    }
    
    return ATLAS_OK;   
}

AtlasError ImageDirReader::Close() {
    DestroyResource();
    return ATLAS_OK;
}
